/** @file errors.c
* Implements all error handling functions declared in errors.h
*
* @author Victor Petrov <victor_petrov@harvard.edu>
* @date   Feb 27, 2011
* @cond
*------------------------------------------------------------------------------
* LEGAL:
*------------------------------------------------------------------------------
*
* Copyright (c) 2011, The President and Fellows of Harvard College
* Copyright (c) 2011, The Neuroinformatics Research Group at Harvard University
* All rights reserved.
*
* License: New BSD License   <http://creativecommons.org/licenses/BSD/>
*-------------------------
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following conditions are met:
*
*	Redistributions of source code must retain the above copyright notice, 
*	this list of conditions and the following disclaimer.
*
*	Redistributions in binary form must reproduce the above copyright notice, 
*	this list of conditions and the following disclaimer in the documentation
*	and/or other materials provided with the distribution.
*
*	Neither the name of the Neuroinformatics Research Group at Harvard, nor 
*	the names of its contributors may be used to endorse or promote products 
*	derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
* POSSIBILITY OF SUCH DAMAGE.
*------------------------------------------------------------------------------
* vim: ts=4
*------------------------------------------------------------------------------
* @endcond
*/

#ifdef PHPEXT
    #include "php.h"
    #include "php_vxp.h"
    #include <zend_interfaces.h>    /* TSRMLS_FETCH() */
    #include <zend_exceptions.h>    /* zend_throw_exception_ex() */
#endif

#include <assert.h>     /* assert() */
#include <stdio.h>      /* fprintf() */
#include "errors.h"     /* enum vxpErrors, function declarations */

#ifndef PHPEXT
    extern void vxpExit(struct MemBlockInfo* mem, int status);
#else
    /** (Flex) gets the user defined value.
    * vxp uses this value to signal to vxp_error() that a
    * VXP_ERROR_BAD_CHAR has occurred and that an XPathException
    * has already been thrown.
    * @param[in] yyscanner An internal Flex data structure
    * @return A value from vxpErrors . Usually, @c VXP_ERROR_BAD_CHAR
    */
    extern struct vxpState* vxp_get_extra(void* yyscanner);
#endif

/** Handles errors generated by Flex/Bison.
* In the PHP extension, this function will throw an XPathException.
* In the vxp utility, this function calls vxpExit().
* @note (PHP only) If Flex encounters an unexpected character, it will call
*       badcharerror() first, which will thrown an XPathException, 
*       but, badcharerror() does not cause vxp_parse() to terminate, and so 
*       Bison calls vxp_error() to complain about an unexpected end of input.
*       To solve this, YY_EXTRA_TYPE and vxp_get_extra() are used to avoid 
*       throwing a second XPathException.
*       When @c vxp_get_extra()->errorKind==VXP_ERROR_BAD_CHAR, vxp_error()
*       will silently return and it will @b not throw an XPathException.
* @param[in] yylloc_param Bison location
* @param[in] yyscanner    Flex internal data structure
* @param[in] root         A pointer to the root Node
* @param[in] mem          The MemBlockInfo struct for handling memory 
* @param[in] message      Bison / Flex error message
* @throw XPathException   (PHP only) XPathException::SYNTAX
* @throw XPathException   (PHP only) XPathException::MEMORY
* @return The return value should be ignored.
* @warning The declaration of vxp_error() must match what Bison generates
*          based on \%parse-param and \%lex-param
* @warning This function will either terminate the vxp program or, for PHP,
*          it will throw an XPathException.
*/
int vxp_error(YYLTYPE *yylloc_param, void* yyscanner, struct Node** root, 
              struct MemBlockInfo* mem, const char* const message)
{
    #ifdef PHPEXT
        zval* exception=NULL;                   /* The exception to throw */
        zval* arg_type=NULL;                    /* Exception code */
        zval* arg_message=NULL;                 /* Exception message */
        zend_class_entry* vxp_except_ce=NULL;   /* Exception class entry */
        long type=VXP_ERROR;                    /* Default error code */

        /* flex threw an exception already via badcharerror() */
        if (vxp_get_extra(yyscanner)->errorKind==VXP_ERROR_BAD_CHAR)
            return 0;               /* avoid throwing another XPathException */

        TSRMLS_FETCH();                         /* Fetch TSRMLS_CC */

        /* see gnu.org/software/bison/manual/html_node/Error-Reporting.html */
        if (message[0]=='s')                    /* syntax error ? */
            type=VXP_ERROR_SYNTAX;
        else
            type=VXP_ERROR_MEMORY;              /* memory exhausted */

        /* Arg 1 */
        MAKE_STD_ZVAL(arg_type);                /* init type argument */
        ZVAL_LONG(arg_type,type);

        /* Arg 2 */
        MAKE_STD_ZVAL(arg_message);             /* init message argument */
        ZVAL_STRING(arg_message,message,1);

        MAKE_STD_ZVAL(exception);               /* init exception object */
        object_init_ex(exception,vxp_xpath_exception_ce);
        vxp_except_ce=Z_OBJCE_P(exception);

        /* call the constructor */
        zend_call_method_with_2_params(&exception,vxp_except_ce,
                                       &vxp_except_ce->constructor,
                                       "__construct",
                                       NULL,
                                       arg_message,
                                       arg_type);
        
        /* Bake the location info into the XPathException object */
        vxp_xpath_exception_set_location(vxp_except_ce,
                                         exception,
                                         yylloc_param->first_line, 
                                         yylloc_param->first_column TSRMLS_CC);

        FREE_ZVAL(arg_type);                    /* cleanup */
        zval_dtor(arg_message);
        FREE_ZVAL(arg_message);

        zend_throw_exception_object(exception TSRMLS_CC); /* throw exception */
    #else
        /* Print error to stderr */
        fprintf(stderr,
                "ERROR: %s at line %d, column %d\n",
                message, yylloc_param->first_line, yylloc_param->first_column);

        vxpExit(mem, EXIT_FAILURE);             /* cleanup and exit */
    #endif

    /* ignored. it's here because yyerror() must return an int */
    return 0;
}

/** Handles memory allocation errors.
* In the PHP extension, this function calls php_error_docref().
* In the vxp utility, this function calls vxpExit().
* @param[in] bytes Number of bytes tried to allocate
* @param[in] func  Name of the function (@c __func__ )
* @param[in] file  Name of the file     (@c __FILE__ )
* @param[in] line  Line number          (@c __LINE__ )
* @param[in] mem   The MemBlockInfo struct reponsible for memory allocations 
* @warning This function terminates the vxp program or the PHP script.
*/
void memerror(  const unsigned int bytes,
                const char* const func,
                const char* const file, 
                const int line,
				struct MemBlockInfo* mem)
{
    #ifdef PHPEXT
        /* Fetch TSRMLS_CC */
        TSRMLS_FETCH();

        /* Trigger an E_ERROR */
        php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to allocate %u bytes"
                         " in function %s(), %s:%d\n",bytes,func,file,line);
    #else
        fprintf(stderr,"FATAL: Failed to (re)allocate memory. "
                "Tried to allocate %u bytes in function %s(), %s:%d\n",
                bytes,func,file,line);

        vxpExit(mem, EXIT_FAILURE);
    #endif
}

/** Handles internal AST errors.
* In the PHP extension, this function throws XPathException.
* In the vxp utility, this function calls vxpExit().
* @see XPathException::ERROR
* @param[in] message The error message
* @param[in] node1   The first node, if available
* @param[in] node2   The second node, if available
* @param[in] mem   The MemBlockInfo struct reponsible for memory allocations 
* @warning This function terminates the vxp program or the PHP script.
* @throws XPathException (for PHP only) XPathException::ERROR
*/
void nodeerror( const char* const message, 
                const struct Node* const node1, 
                const struct Node* const node2,
				struct MemBlockInfo* mem)
{
    #ifdef PHPEXT
        TSRMLS_FETCH();     /* fetch TSRMLS_CC */
    #endif

    if (node2)              /* Do we have info about 2 nodes? */
    {
        #ifdef PHPEXT
            /* Throw an XPathException with code XPathException::ERROR */
            zend_throw_exception_ex(vxp_xpath_exception_ce,VXP_ERROR TSRMLS_CC,
                                    "Node '%s' %s Node '%s'\n",
                                     node1->name,message,node2->name);
        #else
            fprintf(stderr,"INTERNAL ERROR: Node '%s' %s Node '%s'\n",
                    node1->name,message,node2->name);
			vxpExit(mem, EXIT_FAILURE);
        #endif
    }
    else
    if (node1)              /* or just 1 node ? */
    {
         #ifdef PHPEXT
            /* Throw an XPathException with code XPathException::ERROR */
            zend_throw_exception_ex(vxp_xpath_exception_ce,VXP_ERROR TSRMLS_CC,
                                    "Node '%s' %s",node1->name,message);
         #else
            fprintf(stderr,"INTERNAL ERROR: Node '%s' %s\n",
                    node1->name,message);
			vxpExit(mem, EXIT_FAILURE);
         #endif
    }
    else                    /* or no nodes at all, just a message? */
    {
         #ifdef PHPEXT
            /* Throw an XPathException with code XPathException::ERROR */
            zend_throw_exception_ex(vxp_xpath_exception_ce,VXP_ERROR TSRMLS_CC,
                                    "%s",message);
         #else
            fprintf(stderr,"INTERNAL ERROR: %s\n",message);
			vxpExit(mem, EXIT_FAILURE);
         #endif
    }
}

/** Handles Flex error, when the XPath contains an invalid character.
* In the PHP extension, this methods throws XPathException.
* In the vxp utility, this function calls vxpExit().
* @see XPathException::BAD_CHAR
* @param[in] invalid  The invalid character
* @param[in] line     The line in the XPath string
* @param[in] col      The column in the XPath string
* @param[in] mem      The MemBlockInfo struct that handles memory
* @throws XPathException (for PHP only) XPathException::BAD_CHAR
*/
void badcharerror(char invalid, int line, int col, struct MemBlockInfo* mem)
{
    #ifdef PHPEXT
        zval* exception=NULL;                   /* the exception */
        zval* arg_type=NULL;                    /* exception code */
        zval* arg_message=NULL;                 /* exception message */
        zend_class_entry* vxp_except_ce=NULL;   /* exception class entry */
        char* message=NULL;                     /* the message (char) */
        long  type=VXP_ERROR_BAD_CHAR;          /* default message type */

        TSRMLS_FETCH();                         /* fetch TSRMLS_CC */

        /* Arg 1 */
        MAKE_STD_ZVAL(arg_type);                /* init code argument */
        ZVAL_LONG(arg_type,type);

        /* Arg 2 */
        message=emalloc(2*sizeof(char));        /* prepare message */
        if (!message)
            memerror(2*sizeof(char),__func__,__FILE__,__LINE__,mem);

        message[0]=invalid;                     /* copy the invalid char */
        message[1]='\0';

        MAKE_STD_ZVAL(arg_message);             /* init message argument */
        ZVAL_STRING(arg_message,message,1);

        MAKE_STD_ZVAL(exception);               /* init exception object */
        object_init_ex(exception,vxp_xpath_exception_ce);
        vxp_except_ce=Z_OBJCE_P(exception);

        /* call the XPathException constructor */
        zend_call_method_with_2_params(&exception,
                                       vxp_except_ce,
                                       &vxp_except_ce->constructor,
                                       "__construct",
                                       NULL,
                                       arg_message,
                                       arg_type);

        /* set the location info */
        vxp_xpath_exception_set_location(vxp_except_ce,
                                         exception,
                                         line,
                                         col TSRMLS_CC);

        FREE_ZVAL(arg_type);                        /* clean up */
        efree(message);
        zval_dtor(arg_message);
        FREE_ZVAL(arg_message);

        zend_throw_exception_object(exception TSRMLS_CC); /* throw exception */
    #else
        fprintf(stderr,"ERROR: Invalid character '%c' encountered at "
                "line %d, column %d.\n",invalid,line,col);
		vxpExit(mem, EXIT_FAILURE);
    #endif
}
